前一篇綜整了三個主要的問題,並針對其中兩個—— significant-text aggregation 的效能和 suggestion 結果單一的問題做了一點點優化,這篇來探討一下 analyzer 設定不理想的問題:
先複習一下目前的設定:
{
  "analysis": {
    "analyzer": {
      "index_analyzer": {
        "type": "custom",
        "tokenizer": "standard",
        "filter": [
          "lowercase",
          "stop"
        ],
        "char_filter": [
          "pun_filter"
        ]
      }
    },
    "char_filter": {
      "pun_filter": {
        "type": "mapping",
        "mappings": [
          "/=>",
          ".=>",
          "ー=>"
        ]
      }
    }
  }
}
先前有提到 ES 的 Text Analysis 會先經過 character filter(char_filter),再經過 tokenizer 分詞、最後經過 token filter(filter)過濾要被索引的 token。
在 char_filter 中,為了避免 standard tokenizer 把 tweet 中的網址非文字的符號當成分詞字元,因此先把 / 、 . 、 ー 移除(透過 mapping 替換成沒有字元),但這個做法蠻手工的,有可能有部分沒有處理到。
另外是可以從上一篇的結果發現,symptom 和 symptom 和 symptoms 會被視為不同的token 但這在 suggestion 中不是太有用。
這篇,我們就針對這兩個問題進行處理。
首先 char_filter 的部分,除了 mapping replacement filter 之外,ES 還有提供 pattern replacement ,透過 regex pattern 來決定哪些 character 會被取代掉,修改後的 analyzer 如下:
{
  "analysis": {
    "analyzer": {
      "index_analyzer": {
        "type": "custom",
        "tokenizer": "standard",
        "filter": [
          "lowercase",
          "stop"
        ],
        "char_filter": [
          "pun_filter"
        ]
      }
    },
    "char_filter": {
      "pun_filter": {
        **"type": "pattern_replace",
        "pattern": "([\\w\\s\\n]+)([\\S\\W]+?)(?=[_\\w\\s\\n]+)",
        "replacement": "$1"**
      }
    }
  }
}
這個 Pattern 主要是把 『文字或空白或換行』後接『非文字』的非文字部分去除掉,所以 http 內的符號都會一並被消除,非常的方便,副作用是其他的符號也會被消除(例如日期字串內的 - 或 / )。
使用其中一筆資料丟入 _analyzer api 示範如下:
GET _analyze
{
"char_filter": [
    {
      "type": "pattern_replace",
      "pattern": "([\\w\\s\\n]+)([^\\w\\s\\n]+?)(?=[_\\w\\s\\n]+)",
      "replacement": "$1"
    }
    ],
  "text":"""epal Daily Update on #COVID19.
31/3/2020 | Lockdown day 8ー
Sources: 
1. https://t.co/s6hO80l4zc, https://t.co/wzozTS77hA
2. https://t.co/36Rp6NmAo1 
3. https://t.co/ly03MoV1Vj
4. https://t.co/OxLSru8Nl4
5. https://t.co/m4aC1zRia8 https://t.co/REQiQceSco"""
}
得到的結果大致還蠻符合預期的:
{
  "tokens": [
    {
      "token": """epal Daily Update on COVID19
3132020  Lockdown day 8
Sources 
1 httpstcos6hO80l4zc httpstcowzozTS77hA
2 httpstco36Rp6NmAo1 
3 httpstcoly03MoV1Vj
4 httpstcoOxLSru8Nl4
5 httpstcom4aC1zRia8 httpstcoREQiQceSco""",
      "start_offset": 0,
      "end_offset": 254,
      "type": "word",
      "position": 0
    }
  ]
}
接下來是要解決重複的 suggestion word 的問題,ES 提供了一個叫做 stemmer 的 token filter,顧名思義是字根的轉換,例如複數型(-s)會被轉換成單數型,動詞過去式也會被轉換為現在式等等;示範如下:
GET _analyze
{
"tokenizer": "standard",
**"filter": {
  "type": "stemmer",
  "language": "light_english"**
},
"char_filter": [
    {
      "type": "pattern_replace",
      "pattern": "([\\w\\s\\n]+)([^\\w\\s\\n]+?)(?=[\\w\\s\\n]+)",
      "replacement": "$1"
    }
    ],
  "text": "docuemnts foxes using drunk"
}
取得的 token 如下:
{
  "tokens": [
    {
      "token": "docuemnt",
      "start_offset": 0,
      "end_offset": 9,
      "type": "<ALPHANUM>",
      "position": 0
    },
    {
      "token": "fox",
      "start_offset": 10,
      "end_offset": 15,
      "type": "<ALPHANUM>",
      "position": 1
    },
    {
      "token": "use",
      "start_offset": 16,
      "end_offset": 21,
      "type": "<ALPHANUM>",
      "position": 2
    },
    {
      "token": "drunk",
      "start_offset": 22,
      "end_offset": 27,
      "type": "<ALPHANUM>",
      "position": 3
    }
  ]
}
雖然大部分的狀況都符合預期,但對於變形的過去式處理似乎沒有很理想。
把上述兩個修改都放入 analyzer settings 後如下:
{
    "analysis": {
      "analyzer": {
        "index_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "stop",
            "stem_filter"
          ],
          "char_filter": [
            "pun_filter"
          ]
        }
      },
      "char_filter": {
        "pun_filter": {
          "type": "pattern_replace",
          "pattern": "([\\w\\s\\n]+)([^\\w\\s\\n]+?)(?=[\\w\\s\\n]+)",
          "replacement": "$1"
        }
      },
      "filter": {
        "stem_filter": {
          "type": "stemmer",
          "language": "light_english"
        }
      }
    }
  }
修改後一樣測試一下:
輸入: cov
輸出:['covid19 percent', 'covid19 coronaviru', 'covid19 mild', 'covid 19']
輸入: covid
輸出:['covid19 percent', 'covid19 coronaviru', 'covid19 mild', 'covid_19 coronaviru', 'covid_19 evirahealth', 'covid_19 coronaupdate']
輸入: covid sym
輸出:['covid symptom mild', 'covid symptom 19', 'covid symptom showing', 'covid symply_tacha 19', 'covid symply_tacha everythintacha']
好的,看起來又更好了一點點,下一篇繼續。